package org.yinxue.swing.unit.model;


import org.yinxue.swing.unit.ast.node.Node;
import org.yinxue.swing.unit.constant.ClassConstant;
import org.yinxue.swing.unit.constant.MethodType;
import org.yinxue.swing.unit.constant.UnitConstant;
import org.yinxue.swing.unit.util.UnitUtil;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author zengjian
 * @create 2018-04-29 9:20
 * @since 1.0.0
 */
public class MethodDesc {

    /**
     * 方法类型
     */
    public MethodType methodType;

    /**
     * 修饰符
     */
    public int modifier;

    /**
     * 方法名称
     */
    public String methodName;

    /**
     * 方法文本行
     */
    public String[] methodLines;

    /**
     * 方法行
     */
    public String methodLine;

    /**
     * 完整的method开头短语， 开始 --> {
     */
    public String methodHeader;

    /**
     * 方法全文
     */
    public String methodContext;

    /**
     * 方法名小写开头
     */
    public String methodVar;

    /**
     * 方法入参 变量类型：值
     */
    public Map<String, String> params;

    /**
     * 返回变量
     */
    public String returnParam;

    /**
     * 返回类型
     */
    public String returnType;

    /**
     * 使用该方法的类
     */
    public ClassDesc parent;

    /**
     * 持有该方法的实际类
     */
    public ClassDesc realParent;

    /**
     * 方法实际类类名
     */
    public String methodClassName;

    /**
     * 方法父类，即需mock的方法所在方法
     */
    public MethodDesc parentMethodDesc;

    /**
     * 方法类包含的语句
     */
    public List<StatementDesc> statements = new CopyOnWriteArrayList();

    /**
     * 方法抛出异常
     */
    public List<String> exceptions = new LinkedList<>();

    /**
     * 增加一个method节点，用来详细解析method内部信息
     */
    public Node methodNode;

    /**
     * 生成的单元测试文本
     */
    public String unitContext;
    public StringBuilder unitVariableLines = new StringBuilder(256);
    public StringBuilder unitMockMethodLines = new StringBuilder(512);
    public StringBuilder unitInvokeLines = new StringBuilder(512);

    /**
     * 方法变量命名去重
     */
    public Map<String, Integer> unitVariableTable = new HashMap<>();

    /**
     * 构造method <br>
     *
     * @param methodLine 方法header
     * @param methodContext 方法语句块
     * @param parent 隶属父类
     */
    public MethodDesc(String methodLine, String methodHeader, String methodContext, ClassDesc parent, MethodType methodType) {
        this.methodLine = methodLine;
        this.methodHeader = methodHeader;
        this.methodContext = methodContext;
        this.methodLines = UnitUtil.parseArray(methodContext);
        this.parent = parent;
        this.methodType = MethodType.CONSTRUCTOR;
        this.initMethodLines();
    }

    /**
     * 构造method <br>
     *
     * @param methodLine 方法Heaer
     * @param methodlines 方法语句数组
     * @param parent 隶属父类
     */
    public MethodDesc(String methodLine, String methodHeader, String[] methodlines, ClassDesc parent, MethodType methodType) {
        this.methodLine = methodLine;
        this.methodHeader = methodHeader;
        this.methodLines = methodlines;
        this.methodContext = UnitUtil.parseContext(methodlines);
        this.parent = parent;
        this.methodType = methodType;
        this.initMethodLines();
    }

    private void initMethodLines() {
        this.methodName = findMethodName(this.methodLine);
        this.methodVar = UnitUtil.toLowerCaseFirstChar(this.methodName);
        this.params = UnitUtil.getMethodEntryParams(this.methodHeader);
        this.exceptions = UnitUtil.getMethodExceptions(this.methodHeader);
        this.returnType = UnitUtil.getReturnType(this.methodLine);
        this.parseNeedMockMethods();
        this.checkModifier();
    }

    private void checkModifier() {
        if (methodLine.contains(" static ")){
            modifier += ClassConstant.STATIC;
        }
    }

    /**
     * 根据所给的方法行，找到相应的方法名 <br>
     *
     * @param methodLine 方法行或者methodPharse 例如: public void setStatus(String status) {
     * @return 方法名
     */
    public static String findMethodName(String methodLine) {
        Pattern pattern = Pattern.compile("[ >]+([a-zA-Z_0-9]+)\\s*\\(");
        Matcher matcher = pattern.matcher(methodLine);
        String methodName = null;
        while (matcher.find()) {
            methodName = matcher.group(1);
        }
        return methodName;
    }


    private String filterAnnotation(String entryParamLine) {
        List<String> fiterTexts = new LinkedList<>();
        Pattern pattern = Pattern.compile(UnitConstant.VARS_ANNOTATION);
        Matcher matcher = pattern.matcher(entryParamLine);
        while (matcher.find()) {
            fiterTexts.add(matcher.group());
        }
        for (String str : fiterTexts) {
            entryParamLine = entryParamLine.replace(str, "");
        }
        return entryParamLine;
    }

    public void parseNeedMockMethods() {
        Pattern pattern = Pattern.compile(UnitConstant.METHOD_NEED_MOCK);
        Matcher matcher = pattern.matcher(this.methodContext);
        List<String> list = new LinkedList<>();
        while (matcher.find()) {
            String needMockMethod = matcher.group();
            if (!list.contains(needMockMethod)) {
                list.add(needMockMethod);
                this.statements.add(new StatementDesc(needMockMethod, this.methodContext, this));
            }
        }
    }

    private String[] getMainMethodEntryParams(String line) {
        return UnitUtil.resolveEntryParamArray(line);
    }

    private List<String> getGenericMethodList(String line) {
        Pattern pattern = Pattern.compile("[A-Za-z][A-Za-z0-9_]*\\.[a-z][A-Za-z0-9_]*\\([A-Za-z0-9_, ]*\\)");
        Matcher matcher = pattern.matcher(line);
        List<String> list = new ArrayList<>();
        while (matcher.find()) {
            list.add(matcher.group());
        }
        return list;
    }

    private String filterLine(String line) {
        return line.replaceAll("\n", "").substring(line.indexOf("(") + 1, line.lastIndexOf(")"));
    }

    private String getParamType(String paramLine, String param) {
        String paramType = paramLine.substring(0, paramLine.lastIndexOf("="));
        paramType = paramType.replaceAll("(=|" + param + ")", "").trim();
        if (paramType.contains("<")) {
            paramType = paramType.replaceAll("<[\\s\\S]*>", "").trim();
        }
        return paramType;
    }

    @Override
    public String toString() {
        return returnType + ' ' + methodName +
                ' ' + params + " mock方法:" + statements.size() + "\n";
    }

    public void refreshReturnParam() {
        for (StatementDesc childMethod : this.statements) {
            // 根据方法行，内部获取准确的返回类型及变量
            childMethod.findReturnParam(childMethod, childMethod.methodLine);
        }

        // 由于存在返回参数的相同，所以需要再开循环解决相同参数的问题
        // 采用Map来判断，如果map中包含了，那就将该变量进行修改，计数相加的方式，同时将子方法中的变量赋值
        Map<String, Integer> countMap = new HashMap<>();
        for (StatementDesc childMethod : this.statements) {
            if (!countMap.containsKey(childMethod.returnParam)) {
                countMap.put(childMethod.returnParam, 1);
            } else {
                int count = countMap.get(childMethod.returnParam);
                count++;
                countMap.put(childMethod.returnParam, (count));
                childMethod.returnParam = childMethod.returnParam + count;
            }
        }
    }

    public String getMethodName(){
        return methodName;
    }

    public int getParentModifier(){
        return this.parent.modifier;
    }

    public boolean isEnumMethod(){
        return (this.parent.modifier & ClassConstant.ENUM) !=0 && !this.parent.enumList.isEmpty();
    }

    public String getParentName(){
        return this.parent.simpleName;
    }

    public String getParentVariable(){
        return this.parent.defaultVariable;
    }

    public String getDefaultEnumName(){
        if (!this.parent.enumList.isEmpty()){
            return this.parent.enumList.get(0);
        }
        return "";
    }

    // 获取参数组合类型串，以，隔开
    public String compositParamType(){
        Iterator<Map.Entry<String,String>> iterator = params.entrySet().iterator();
        StringBuilder builder = new StringBuilder(16);
        while (iterator.hasNext()){
            builder.append(iterator.next().getValue());
            if (iterator.hasNext()){
                builder.append(",");
            }
        }
        return builder.toString();
    }

}
